iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0

參考來源 : https://arrow-kt.io/learn/immutable-data/intro/

不可變的領域模型的一點點麻煩

前兩天提到的 Data Class, Sealed Class, Enum 與 Value Class 配合不可變的變數。是在 Kotlin 中作 Domain Modeling 的良好方法。複習一下前幾天的文章

所以如果我們想要精確地建模一個領域,我們經常會得到大量的巢狀類,每一個都代表一種特定的物件模型

data class Person(val name: String, val age: Int, val address: Address)
data class Address(val street: Street, val city: City)
data class Street(val name: String, val number: Int?)
data class City(val name: String, val country: String)

雖然 Kotlin 有提供 data class 的 copy, 但只限第一層。如果我們只想轉變(Transform - aka 修改)一個第三層的值,我們還是需要進行多次的複製。

fun Person.capitalizeCountry(): Person =
  this.copy(
    address = address.copy(
      city = address.city.copy(
        country = address.city.country.capitalize()
      )
    )
  )

Arrow KT Optics

Arrow KT 為這個問題提供了一個解法,即 optics。 optiocs 是代表在更大的型別對內部的一個(或多個)值的訪問的值。可以用光學的透鏡來類比。所以取 Optics 這個名字

https://ithelp.ithome.com.tw/upload/images/20231005/20135701kuiub2mMpW.png

例如,我們可能有一個焦點在 Person 的 address field 上。通過組合不同的光學,我們可以關注巢狀的元素,例如 Person 中的 address 中的 city 。程式碼會比描述更有說服力,所以讓我們看看使用 optics 後上面的例子如何改進。

使用 Arrow Optics 最簡單的方法是通過其編譯器 plugins 。在把它到您的 gradle,您只需要標記每個希望生成光學的類,使用 @optics 注解。


import arrow.optics.*

@optics data class Person(val name: String, val age: Int, val address: Address) {
  companion object
}
@optics data class Address(val street: Street, val city: City) {
  companion object
}
@optics data class Street(val name: String, val number: Int?) {
  companion object
}
@optics data class City(val name: String, val country: String) {
  companion object
}

煩人的 compaion object : 要在每個類中都有一個,即使它是空的。這是由於 KSP 的限制

要先 Compile 一次

因為 optics 會自動生成程式,所以要先作一次 compiler 才能用到 optics 的 method

Optics 為每個 field 生成 optics ,可以在 class 下使用。例如,Person.address 是聚焦在 address 上的Optics。此外,可以使用 "." 一路到你想關注的欄位。在這種情況下,Person.address.city.country 代表正確聚焦在我們想要轉換的欄位上。使用它,我們可以用兩種方式重新實現 capitalizeCountry:

  1. Optics 操作
fun Person.capitalizeCountryModify(): Person =
  Person.address.city.country.modify(this) { it.capitalize() }
  1. copy builder
    Arrow Optics 提供了一個 copy 的 overload,不是命名參數,而是接受一個區塊。在該區塊內,可以使用 optic transform 的操作

fun Person.capitalizeCountryCopy(): Person =
  this.copy {
    Person.address.city.country transform { it.capitalize() }
  }

每日一推(G)I-DLE

My Bag 雖然前幾天推過了,但這個版本的舞台很棒

Yes


上一篇
D20: 寫在 JCconf 前 - Kotlin Data Class 與 Java Records 都是 Product Type
下一篇
D22: 雲原生 Kotlin 也有份? 談 Arrow KT Resilience
系列文
讓 Kotlin 程式碼更道地 - 談 Effective Kotlin 與相關的 Design Pattern30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言